
#ifndef __OOPLESS_PARSER__
#define __OOPLESS_PARSER__

#include "planes.h"
#include "worldspawn.h"
#include <string>
#include <vector>
#include <fstream>
#include <queue>
#include "EntityConverter.hpp"

bool is_ebrush(const std::vector<std::string> &);

std::vector<std::string> extract_brush(const std::vector<std::string> &);

struct TBrush override_textures(struct TBrush, const char*);

bool write(const struct TBrush &, 
           std::ofstream &,
           void (*f) (std::stringstream &, const std::vector<TPlanePoints> &));

void write(const std::vector<std::string> &, 
           std::ofstream &,
           void (*f) (std::stringstream &, const std::vector<TPlanePoints> &),
           EntityConverter &);

bool convertmap(std::stringstream &, 
                const char * const,
                void (*f) (std::stringstream &, const std::vector<TPlanePoints> &),
                std::vector<std::vector<std::string> > &);


#define KEYWORD_ENTITY      "entity"
#define KEYWORD_BRUSH       "brush"
#define KEYWORD_VERTICES    "vertices"
#define KEYWORD_FACES       "faces"
#define KEYWORD_GLOBAL      "global"
#define KEYWORD_PREFAB      "prefab"

template <class STREAMOBJ>
std::vector<std::string> get_entity(STREAMOBJ &f) {
using namespace std;
    vector<string> output;
    string line;
    unsigned int pos = f.tellg();
    while (getline(f, line)) {
        // stop when
        // entity or a prefab keyword
        // brush keyword and the entity is not of the type teleporter or jumppad.
        if ((line.find(KEYWORD_ENTITY) != string::npos || 
            line.find(KEYWORD_PREFAB) != string::npos) || (
            line.find(KEYWORD_BRUSH) != string::npos && !is_ebrush(output))) {
                f.seekg(pos);
                break;
        } else {
            output.push_back(line);
        }
        pos = f.tellg();
    }
    return output;
}

template <class STREAMOBJ>
std::vector<Eigen::Vector3f> parse_vertices(STREAMOBJ &f) {
using namespace std;
#define FIRSTCH(x) x[0]
    vector<Eigen::Vector3f> output;
    string line;
    unsigned int pos = f.tellg();
    while(getline(f, line)) {
        if (line.find(KEYWORD_FACES) != string::npos || 
            line.find(KEYWORD_BRUSH) != string::npos ||
            line.find(KEYWORD_ENTITY) != string::npos ||
            line.find(KEYWORD_PREFAB) != string::npos) {
                f.seekg(pos);
                return output;
        } else {
            Eigen::Vector3f v;
            stringstream ss(line);
            string vdata;
            unsigned int i = 0;
            while (ss >> vdata) {
                if (isdigit(FIRSTCH(vdata)) || FIRSTCH(vdata) == '-') {
                    double dvalue = stod(vdata);
                    v[i] = (float)dvalue;
                }
                // note: this prevents the index growing out of vector capacity
                // alternatively can throw an exception when that happens instead
                // to make it clear that an illegal line has been hit.
                i = (i + 1) % 3;
            }
            output.push_back(v);
        }
        pos = f.tellg();
    }
    return output;
}

template <class STREAMOBJ>
std::vector<struct TFace> parse_face(STREAMOBJ &f) {
using namespace std;
#define FIRSTCH(x) x[0]
#define SECONDCH(x) x[1]
    // it is possible for the next line to be unrelated to faces
    // so it is needed to reset the stream prior to reading 
    // the new label, e.g '\tbrush'.
    vector<struct TFace> output;
    string line;
    unsigned int pos = f.tellg();
    while(getline(f, line)) {
        if (line.find(KEYWORD_VERTICES) != string::npos || 
            line.find(KEYWORD_BRUSH) != string::npos ||
            line.find(KEYWORD_ENTITY) != string::npos ||
            line.find(KEYWORD_PREFAB) != string::npos ||
            line.find(KEYWORD_GLOBAL) != string::npos) {
                f.seekg(pos);
                return output;
        } else {
                struct TFace x;
                float *f = &x.m_fXOffset;;
                stringstream ss(line);
                string fdata;
                bool hex = false;
                unsigned int i = 0;
                while (ss >> fdata) {
                    if (i < 5) {
                        if (isdigit(FIRSTCH(fdata)) || FIRSTCH(fdata) == '-') {
                            double dvalue = stod(fdata);
                            *f = (float)dvalue;
                            f++;
                        }
                        // note: if there is a non-digit in the first 5 fields
                        // then it qualifies as an illegal line.
                    } else if (4 < i && i < 8) {
                            x.m_Indices.push_back(stoi(fdata));
                    } else if (8 <= i) {
                        if (!hex && fdata.length() > 1) {
                            if (SECONDCH(fdata) == 'x') {
                            // this is the unidentified hex digit.
                            // out of range for stoi
                                x.hex = fdata;
                                hex = true; 
                            }
                        } else if (!hex) {
                            x.m_Indices.push_back(stoi(fdata));
                        } else if (hex) {
                            x.m_Material = fdata;
                        } else {
                            ;
                        }
                    }
                    i++;
                } // end, per field iteration
                output.push_back(x);
        } // end else case, if line did not contain other keywords 
        pos = f.tellg();
    } // end, per line iteration
    // the odd case when the map file ends with a face indent.
    return output;
}

template <class STREAMOBJ>
struct TBrush parse_brush(STREAMOBJ &f) {
using namespace std;
    struct TBrush output;
    string l;
    getline(f, l);
    if (l.find(KEYWORD_VERTICES) != string::npos) {
        output.m_Vertices = parse_vertices<STREAMOBJ>(f);        
    } 
    getline(f, l);
    if (l.find(KEYWORD_FACES) != string::npos) {
        output.m_Faces = parse_face<STREAMOBJ>(f);
    }
    return output;
}

template <class ISTR>
void parse_prefab(ISTR &f, 
                  std::ofstream &out, 
                  void (*b) (std::stringstream &, const std::vector<TPlanePoints> &),
                  std::vector<std::vector<std::string> > &entities) {
    using namespace std;
    string l;
    getline(f, l);
    if (l.find(KEYWORD_BRUSH) != string::npos) {
        write(parse_brush<ISTR>(f), out, b);
    } else if (l.find(KEYWORD_ENTITY) != string::npos) {
        entities.push_back(get_entity<ISTR>(f));
    }
}

#endif
